package com.baijiaoxi.utils.qcloud;

import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import com.baijiaoxi.common.model.base.CommonError;
import com.baijiaoxi.common.model.base.CoreException;
import com.baijiaoxi.common.utils.jdk.Strings;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static com.baijiaoxi.utils.qcloud.ContentTypeUtil.getContentTypeByFileName;

/**
 * 腾讯云对象存储工具类
 */
@Slf4j
public class AliyunOSSHelper implements OSHelper, AutoCloseable {

    private OSS ossClient;
    private String bucketName;
    private String endpoint;
    private boolean shutdownAfterUseStat = true;

    public AliyunOSSHelper(OSS ossClient, String endpoint, String bucketName) {
        this.ossClient = ossClient;
        this.endpoint = endpoint;
        this.bucketName = bucketName;
    }

    @Override
    public boolean isShutdownAfterUseStat() {
        return shutdownAfterUseStat;
    }

    @Override
    public OSHelper setShutdownAfterUseStat(boolean shutdownAfterUseStat) {
        this.shutdownAfterUseStat = shutdownAfterUseStat;
        return this;
    }

    @Override
    public void close() {
        try {
            ossClient.shutdown();
        } catch (Exception e) {
            log.error("ossClient关闭异常", e);
        }
    }


    private void ifShutdown(boolean shutdownAfterUseStat) {
        if (shutdownAfterUseStat)
            close();
    }

    @Override
    public String getSignUploadUrl(String fileKey, String contentType, boolean shutdownAfterUseStat) {
        try {
            Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

            // 生成预签名URL。
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, fileKey, HttpMethod.PUT);
            // 设置过期时间。
            request.setExpiration(expiration);
            request.setContentType(Strings.isBlank(contentType) ? "application/x-www-form-urlencoded" : contentType);
            // 通过HTTP PUT请求生成预签名URL。
            URL signedUrl = ossClient.generatePresignedUrl(request);
            return signedUrl.toString();
        } catch (Exception e) {
            throw CoreException.create(CommonError.ossSignUploadUrlFail, e);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }

    }

    /**
     * 上传
     *
     * @param key
     * @param url
     * @param shutdownAfterUseStat
     */
    @Override
    public void upload(String key, String originFilename, String url, boolean shutdownAfterUseStat, boolean addMeta) {
        try {
            ObjectMetadata meta = createMeta(key, originFilename, addMeta);
            InputStream inputStream = new URL(url).openStream();
            ossClient.putObject(bucketName, key, inputStream, meta);
            inputStream.close();
        } catch (Exception e) {
            throw CoreException.create(CommonError.ossUploadError, e);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }


    /**
     * 上传
     *
     * @param key
     * @param localFile
     * @param shutdownAfterUseStat
     */
    @Override
    public void upload(String key, String originFilename, File localFile, boolean shutdownAfterUseStat, boolean addMeta) {
        try {
            upload(key, originFilename, new FileInputStream(localFile), shutdownAfterUseStat, addMeta);
        } catch (FileNotFoundException e) {
            throw CoreException.create(CommonError.ossUploadErrorFileNotExists, e, localFile.getAbsolutePath());
        } catch (Exception e) {
            throw CoreException.create(CommonError.ossUploadError, e, localFile.getAbsolutePath());
        }
    }

    /**
     * 上传
     *
     * @param key
     * @param localFile
     * @param shutdownAfterUseStat
     */
    @Override
    public void uploadWithPart(String key, String originFilename, File localFile, boolean shutdownAfterUseStat, boolean addMeta) {
        if (localFile.length() > partLimit) {
            // 创建InitiateMultipartUploadRequest对象。
            ObjectMetadata meta = createMeta(key, originFilename, addMeta);
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key, meta);
            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个uploadId发起相关的操作，如取消分片上传、查询分片上传等。
            String uploadId = upresult.getUploadId();
            try {
                // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
                List<PartETag> partETags = new ArrayList<PartETag>();
                // 计算文件有多少个分片。
                long fileLength = localFile.length();
                int partCount = (int) (fileLength / partSize);
                if (fileLength % partSize != 0) {
                    partCount++;
                }
                // 遍历分片上传。
                for (int i = 0; i < partCount; i++) {
                    long startPos = i * partSize;
                    long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                    InputStream instream = new FileInputStream(localFile);
                    // 跳过已经上传的分片。
                    instream.skip(startPos);
                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
                    uploadPartRequest.setBucketName(bucketName);
                    uploadPartRequest.setKey(key);
                    uploadPartRequest.setUploadId(uploadId);
                    uploadPartRequest.setInputStream(instream);
                    // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                    uploadPartRequest.setPartSize(curPartSize);
                    // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
                    uploadPartRequest.setPartNumber(i + 1);
                    // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                    UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                    // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                    partETags.add(uploadPartResult.getPartETag());
                    instream.close();
                }


                // 创建CompleteMultipartUploadRequest对象。
                // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
                CompleteMultipartUploadRequest completeMultipartUploadRequest =
                        new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
                CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            } catch (Exception e) {
                log.error("分块上传阿里云失败：", e);
                // 取消分片上传，其中uploadId源自InitiateMultipartUpload。
                AbortMultipartUploadRequest abortMultipartUploadRequest =
                        new AbortMultipartUploadRequest(bucketName, key, "<uploadId>");
                ossClient.abortMultipartUpload(abortMultipartUploadRequest);
                upload(key, originFilename, localFile, shutdownAfterUseStat, addMeta);
            } finally {
                ifShutdown(shutdownAfterUseStat);
            }
        } else upload(key, originFilename, localFile, shutdownAfterUseStat, addMeta);
    }

    /**
     * 上传
     * 流式
     *
     * @param key
     * @param inputStream
     * @param shutdownAfterUseStat
     */
    @Override
    public void upload(String key, String originFilename, InputStream inputStream, boolean shutdownAfterUseStat, boolean addMeta) {
        ObjectMetadata meta = createMeta(key, originFilename, addMeta);
        try {
            ossClient.putObject(bucketName, key, inputStream, meta);
            inputStream.close();
        } catch (Exception e) {
            throw CoreException.create(CommonError.ossUploadError, e);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }

    /**
     * 获取有过期时间的链接
     *
     * @param key
     * @param expirationDate
     * @param shutdownAfterUseStat
     * @return
     */
    @Override
    public String getUrl(String key, Date expirationDate, boolean shutdownAfterUseStat) {
        try {
            // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
            URL url = ossClient.generatePresignedUrl(bucketName, key, expirationDate);
            return url.toString();
        } catch (Exception e) {
            throw CoreException.create(CommonError.ossSignUrlError, e, key, expirationDate);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }

    /**
     * 删除
     *
     * @param key
     * @param shutdownAfterUseStat
     */
    @Override
    public void del(String key, boolean shutdownAfterUseStat) {
        try {
            // 删除文件。如需删除文件夹，请将ObjectName设置为对应的文件夹名称。如果文件夹非空，则需要将文件夹下的所有object删除后才能删除该文件夹。
            ossClient.deleteObject(bucketName, key);
        } catch (Exception e) {
            throw CoreException.create(CommonError.ossDelFileError, e, bucketName, key);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }

    public String getStaticUrl(String fileKey) {
        return "https://" + bucketName + "." + endpoint + "/" + fileKey;
    }


    /**
     * 获取对象信息
     *
     * @param fileKey
     * @param shutdownAfterUseStat
     * @return
     */
    @Override
    public ObjectInfo getObjectInfo(String fileKey, boolean shutdownAfterUseStat) {
        ObjectInfo objectInfo = new ObjectInfo();
        ObjectMetadata metadata = ossClient.getObjectMetadata(bucketName, fileKey);
        ifShutdown(shutdownAfterUseStat);
        objectInfo.setContentType(metadata.getContentType());
        objectInfo.setSize(metadata.getContentLength());
        return objectInfo;
    }


    /**
     * 修改文件头
     *
     * @param fileKey
     * @param downloadName
     * @param cacheControl
     * @param shutdownAfterUseStat
     */
    @Override
    public void modifyContentType(String fileKey, String downloadName, String cacheControl, boolean shutdownAfterUseStat) {
        CopyObjectRequest request = new CopyObjectRequest(bucketName, fileKey, bucketName, fileKey);
        ObjectMetadata meta = createMeta(fileKey, downloadName, true);
        request.setNewObjectMetadata(meta);
        // 修改元信息。
        ossClient.copyObject(request);
        ifShutdown(shutdownAfterUseStat);
    }

    private ObjectMetadata createMeta(String fileKey, String originFilename, boolean addMeta) {
        ObjectMetadata meta = new ObjectMetadata();
        meta.setContentType(getContentTypeByFileName(fileKey));
        if (addMeta)
            meta.setContentDisposition(String.format("attachment;filename=\"%s\"", URLEncoder.encode(originFilename, StandardCharsets.UTF_8)));
        return meta;
    }

    @Override
    public void clearContentDisposition(String fileKey, boolean shutdownAfterUseStat) {
        ObjectMetadata meta = new ObjectMetadata();
        meta.setContentType(getContentTypeByFileName(fileKey));
        meta.setContentDisposition("");
        CopyObjectRequest request = new CopyObjectRequest(bucketName, fileKey, bucketName, fileKey);
        request.setNewObjectMetadata(meta);
        // 修改元信息。
        ossClient.copyObject(request);
        ifShutdown(shutdownAfterUseStat);
    }

    /**
     * 下载文件
     *
     * @param fileKey
     * @param fullLocalPath
     */
    @Override
    public void download(String fileKey, String fullLocalPath, boolean shutdownAfterUseStat) {
        ossClient.getObject(new GetObjectRequest(bucketName, fileKey), new File(fullLocalPath));
        ifShutdown(shutdownAfterUseStat);
    }
}
